package com.huawei.java.main.algorithm;

import com.huawei.java.main.Input;
import com.huawei.java.main.Output;
import com.huawei.java.main.classes.*;

import java.util.*;
import java.util.stream.Collectors;

public class Solve {
    private final List<ServerInstance> boughtServers;
    private final Map<Integer, Integer> deployInfo;
    private final List<List<Request>> requests;
    private final Output output;
    private final Map<String, Service> serviceMap;
    private final List<Server> serversSortByCpu;
    private final List<Server> serversSortByMemory;

    public Solve(Input input) {
        output = new Output();
        boughtServers = new ArrayList<>();
        deployInfo = new HashMap<>();
        requests = input.getRequests();
        this.serviceMap = Arrange.getServiceMap(input.getServices());
        this.serversSortByCpu = Arrange.sortByCpuCostEffective(input.getServers(), input.getDays());
        this.serversSortByMemory = Arrange.sortByMemoryCostEffective(input.getServers(), input.getDays());
    }

    public List<Integer> delPosition(List<Request> requests) {
        List<Integer> position = new ArrayList<>();
        for (int i = 0; i < requests.size(); i++) {
            if (!requests.get(i).isAdd()) {
                position.add(i);
            }
        }
        return position;
    }

    public ServerInstance searchPlace(Service service) {
        for (ServerInstance boughtServer : this.boughtServers) {
            boolean fit;
            if (service.isDoubleDeploy()) {
                fit = boughtServer.fitService(service, true);
            } else {
                fit = boughtServer.fitService(service, true)
                        || boughtServer.fitService(service, false);
            }
            if (fit) {
                return boughtServer;
            }
        }
        return null;
    }

    public ServerInstance boughtNewServer(Service service, double err) {
        double resourceRatio = (double) service.getCpu() / service.getMemory();
        if (resourceRatio < 1.0) {
            for (Server server : serversSortByMemory) {
                if (Math.abs(server.getRatio() - resourceRatio) < err && server.fitService(service)) {
                    return server.createInstance();
                }
            }
            for (Server server : serversSortByMemory) {
                if (server.fitService(service)) {
                    return server.createInstance();
                }
            }
        } else {
            for (Server server : serversSortByCpu) {
                if (Math.abs(server.getRatio() - resourceRatio) < err && server.fitService(service)) {
                    return server.createInstance();
                }
            }
            for (Server server : serversSortByCpu) {
                if (server.fitService(service)) {
                    return server.createInstance();
                }
            }
        }
        return null;
    }

    public List<Deploy> solveAddRequest(List<Request> requests) {
        List<Deploy> deploys = new ArrayList<>(Collections.nCopies(requests.size(), null));

        Map<Request, Integer> indexInfo = new HashMap<>();
        for (int i = 0; i < requests.size(); i++) {
            indexInfo.put(requests.get(i), i);
        }

        final List<Request> sortedRequests = requests.stream()
                .sorted(Comparator
                        .comparing((Request request) -> {
                            final Service service = this.serviceMap.get(request.getServiceType());
                            return service.isDoubleDeploy();
                        })
                        .thenComparing((Request request) -> {
                            final Service service = this.serviceMap.get(request.getServiceType());
                            return service.getCpu() + service.getMemory();
                        }).reversed()
                )
                .collect(Collectors.toList());

        for (Request request : sortedRequests) {
            final Service service = this.serviceMap.get(request.getServiceType());
            final ServerInstance serverInstance = searchPlace(service);
            if (serverInstance != null) {
                if (service.isDoubleDeploy()) {
                    serverInstance.addService(service, request.getServiceId(), true);
                    deploys.set(indexInfo.get(request), new Deploy(serverInstance.getId(), null));
                } else if (serverInstance.fitService(service, true)) {
                    serverInstance.addService(service, request.getServiceId(), true);
                    deploys.set(indexInfo.get(request), new Deploy(serverInstance.getId(), true));
                } else {
                    serverInstance.addService(service, request.getServiceId(), false);
                    deploys.set(indexInfo.get(request), new Deploy(serverInstance.getId(), false));
                }
                deployInfo.put(request.getServiceId(), serverInstance.getId());
            } else {
                final ServerInstance boughtNewServer = boughtNewServer(service, 0.2);
                boughtServers.add(boughtNewServer);
                boughtNewServer.addService(service, request.getServiceId(), true);

                final Boolean deployA = service.isDoubleDeploy() ? null : true;
                deploys.set(indexInfo.get(request), new Deploy(boughtNewServer.getId(), deployA));
                deployInfo.put(request.getServiceId(), boughtNewServer.getId());
            }
        }
        return deploys;
    }

    public void solveOneDay(List<Request> requests) {
        final int haveBought = boughtServers.size();
        List<Deploy> allDeploys = new ArrayList<>();
        final List<Integer> delPosition = delPosition(requests);
        delPosition.add(requests.size());
        int start = 0;
        for (final int end : delPosition) {
            final List<Request> addRequests = requests.subList(start, end);
            if (addRequests.size() != 0) {
                final List<Deploy> deploys = solveAddRequest(addRequests);
                allDeploys.addAll(deploys);
            }
            if (end == requests.size()) {
                continue;
            }
            final Request request = requests.get(end);
            final int delServiceId = request.getServiceId();
            final Integer deployServerId = deployInfo.get(delServiceId);
            boughtServers.get(deployServerId).delService(delServiceId);
            start = end + 1;
        }
        final List<ServerInstance> boughtOneDay = boughtServers.subList(haveBought, boughtServers.size());
        output.addDay(new Day(boughtOneDay, allDeploys, new ArrayList<>()));
    }

    public void solve() {
        for (List<Request> requests : requests) {
            solveOneDay(requests);
        }
    }

    public Output getOutput() {
        return output;
    }
}
